/*
 * GPUC
 * 
 * Copyright © 2008 Attila T. Áfra <attila.afra@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */

#ifdef _DEBUG
#include <stdio.h>
#endif

#include <stdlib.h>
#include <string.h>
#include "Shaders.h"
#include "StreamPrivate.h"
#include "KernelPrivate.h"

/*------------------------------------------------------------------------------------------------*/

static GPUC_Bool
GPUC_uniformToParameterType(GLenum     uniformType,
                            GPUC_Type *parameterType);

static void
GPUC_Kernel_internalInvoke(GPUC_Kernel           kernel,
                           const GPUC_Rectangle *range);

static GPUC_Bool
GPUC_Kernel_setInputPrologue(GPUC_Kernel kernel,
                             int         parameterIndex,
                             GPUC_Type   parameterType);

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Kernel
GPUC_Kernel_create(const char *sourceCode)
{
    GPUC_Kernel  kernel;
    const char  *sourceCodeArray[] = {GPUC_fragmentShaderSourceCodePrologue, sourceCode};
    GLint        compileStatus;
    GLint        uniformCount;
    GLint        uniformSize;
    GLenum       uniformType;
    GLchar      *uniformName;
    GLint        uniformNameSizeMax;
    GLint        uniformLocation;
    int          parameterIndex = 0;
    GPUC_Type    parameterType;
    GLint       *samplers;
    int          i;

#ifdef _DEBUG
    GLchar *infoLog;
    GLint   infoLogSize;
#endif

    if (!GPUC_initialized)
        return NULL;

    // Allocate memory for the kernel object (1)
    kernel = (GPUC_Kernel)malloc(sizeof (struct GPUC_Kernel_Object));

    if (kernel == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        return NULL;
    }

    // Create the fragment shader (2)
    kernel->fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);

    if (kernel->fragmentShaderId == 0)
    {
        GPUC_error = GPUC_Error_Unknown;
        goto cleanup1;
    }

    // Compile the shader
    glShaderSource(kernel->fragmentShaderId, 2, (const GLchar **)sourceCodeArray, 0);
    glCompileShader(kernel->fragmentShaderId);

#ifdef _DEBUG
    // Write the shader info log to stderr
    glGetShaderiv(kernel->fragmentShaderId, GL_INFO_LOG_LENGTH, &infoLogSize);
    infoLog = (GLchar *)malloc(infoLogSize * sizeof(GLchar));
    
    if (infoLog != NULL)
    {
        glGetShaderInfoLog(kernel->fragmentShaderId, infoLogSize, 0, infoLog);
        fprintf(stderr, "%s", infoLog);
        free(infoLog);
    }
#endif

    glGetShaderiv(kernel->fragmentShaderId, GL_COMPILE_STATUS, &compileStatus);

    if (compileStatus == GL_FALSE)
    {
        GPUC_error = GPUC_Error_CompileFailed;
        goto cleanup2;
    }

    // Create the program (3)
    kernel->programId = glCreateProgram();

    if (kernel->programId == 0)
    {
        GPUC_error = GPUC_Error_Unknown;
        goto cleanup2;
    }

    // Attach the shaders (4)
    glAttachShader(kernel->programId, kernel->fragmentShaderId);
    glAttachShader(kernel->programId, GPUC_vertexShaderId);

    // Link the program
    glLinkProgram(kernel->programId);
    glGetProgramiv(kernel->programId, GL_LINK_STATUS, &compileStatus);

    if (compileStatus == GL_FALSE)
    {
#ifdef _DEBUG
        // Write the program info log to stderr
        glGetProgramiv(kernel->programId, GL_INFO_LOG_LENGTH, &infoLogSize);
        infoLog = (GLchar *)malloc(infoLogSize * sizeof(GLchar));
        
        if (infoLog != NULL)
        {
            glGetProgramInfoLog(kernel->programId, infoLogSize, 0, infoLog);
            fprintf(stderr, "%s", infoLog);
            free(infoLog);
        }
#endif

        GPUC_error = GPUC_Error_CompileFailed;
        goto cleanup4;
    }

    // Create the parameter array (5)
    glGetProgramiv(kernel->programId, GL_ACTIVE_UNIFORMS, &uniformCount);
    kernel->parameters = (GPUC_Parameter *)malloc(uniformCount * sizeof (GPUC_Parameter));

    if (kernel->parameters == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        goto cleanup4;
    }

    // Allocate memory for the uniform name (6)
    glGetProgramiv(kernel->programId, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNameSizeMax);
    uniformName = (GLchar *)malloc(uniformNameSizeMax * sizeof (GLchar));

    if (uniformName == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        goto cleanup5;
    }

    // Create the sampler array (7)
    samplers = (GLint *)malloc(GPUC_maxTextureImageUnitCount * sizeof (GLint));

    if (samplers == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        goto cleanup6;
    }

    // Bind the program and initialize the parameter array (8)
    GPUC_ignoreGlErrors();
    glUseProgram(kernel->programId);

    if (GPUC_isGlError())
    {
        GPUC_error = GPUC_Error_Unknown;
        goto cleanup8;
    }

    // Get the parameters
    kernel->inputStreamCount = 0;

    for (; parameterIndex < uniformCount; ++parameterIndex)
    {
        glGetActiveUniform(kernel->programId, (GLuint)parameterIndex, uniformNameSizeMax,
                           0, &uniformSize, &uniformType,
                           uniformName);

        if (!GPUC_uniformToParameterType(uniformType, &parameterType))
        {
            GPUC_error = GPUC_Error_CompileFailed;
            goto cleanup8;
        }

        uniformLocation = glGetUniformLocation(kernel->programId, uniformName);

        if (parameterType == GPUC_Type_Stream)
        {
            if (kernel->inputStreamCount + (int)uniformSize > GPUC_maxTextureImageUnitCount)
            {
                GPUC_error = GPUC_Error_UnsupportedStreamCount;
                goto cleanup8;
            }

            kernel->parameters[parameterIndex].offset = kernel->inputStreamCount;

            for (i = 0; i < (int)uniformSize; ++i)
                samplers[i] = kernel->inputStreamCount++;

            glUniform1iv(uniformLocation, (GLsizei)uniformSize, samplers);
        }

        kernel->parameters[parameterIndex].name = strdup(uniformName);

        if (kernel->parameters[parameterIndex].name == NULL)
        {
            GPUC_error = GPUC_Error_OutOfMemory;
            goto cleanup8;
        }

        kernel->parameters[parameterIndex].type = parameterType;
        kernel->parameters[parameterIndex].length = (int)uniformSize;
        kernel->parameters[parameterIndex].uniformLocation = uniformLocation;
    }

    kernel->parameterCount = (int)uniformCount;

    // Create the input stream array (9)
    kernel->inputStreams = (GPUC_Stream *)malloc(kernel->inputStreamCount * sizeof (GPUC_Stream));

    if (kernel->inputStreams == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        goto cleanup8;
    }

    for (i = 0; i < kernel->inputStreamCount; ++i)
        kernel->inputStreams[i] = NULL;
    
    // Create the output stream array (10)
    kernel->outputStreams = (GPUC_Stream *)malloc(GPUC_maxDrawBufferCount * sizeof (GPUC_Stream));
    
    if (kernel->outputStreams == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        goto cleanup9;
    }
    
    kernel->outputStreamCount = 0;

    // Success
    free(samplers);
    free(uniformName);

    ++GPUC_objectCount;
    GPUC_error = GPUC_Error_None;
    return kernel;

    // Error
/*
cleanup10:
    free(kernel->outputStreams);
*/
    
cleanup9:
    free(kernel->inputStreams);

cleanup8:
    for (i = 0; i < parameterIndex; ++i)
        free(kernel->parameters[i].name);

    glUseProgram(0);

/*cleanup7:*/
    free(samplers);

cleanup6:
    free(uniformName);

cleanup5:
    free(kernel->parameters);

cleanup4:
    glDetachShader(kernel->programId, GPUC_vertexShaderId);
    glDetachShader(kernel->programId, kernel->fragmentShaderId);

/*cleanup3:*/
    glDeleteProgram(kernel->programId);

cleanup2:
    glDeleteShader(kernel->fragmentShaderId);

cleanup1:
    free(kernel);

    return NULL;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_destroy(GPUC_Kernel kernel)
{
    int i;
    
    glUseProgram(0);
    
    for (i = 0; i < kernel->outputStreamCount; ++i)
        GPUC_Stream_destroy(kernel->outputStreams[i]);
    
    free(kernel->outputStreams);

    for (i = 0; i < kernel->inputStreamCount; ++i)
    {
        if (kernel->inputStreams[i] != NULL)
            GPUC_Stream_destroy(kernel->inputStreams[i]);
    }

    free(kernel->inputStreams);

    for (i = 0; i < kernel->parameterCount; ++i)
        free(kernel->parameters[i].name);

    free(kernel->parameters);

    glDetachShader(kernel->programId, GPUC_vertexShaderId);
    glDetachShader(kernel->programId, kernel->fragmentShaderId);
    glDeleteProgram(kernel->programId);
    glDeleteShader(kernel->fragmentShaderId);

    free(kernel);
    --GPUC_objectCount;
    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Kernel_getMaxOutputStreamCount()
{
    if (!GPUC_initialized)
        return -1;
    
    GPUC_error = GPUC_Error_None;
    return GPUC_maxDrawBufferCount;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Kernel_getParameterCount(GPUC_Kernel kernel)
{
    GPUC_error = GPUC_Error_None;
    return kernel->parameterCount;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Kernel_getParameterIndex(GPUC_Kernel  kernel,
                              const char  *parameterName)
{
    int i;

    for (i = 0; i < kernel->parameterCount; ++i)
    {
        if (strcmp(kernel->parameters[i].name, parameterName) == 0)
        {
            GPUC_error = GPUC_Error_None;
            return i;
        }
    }

    GPUC_error = GPUC_Error_InvalidArgument;
    return -1;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API const char *
GPUC_Kernel_getParameterName(GPUC_Kernel kernel,
                             int         parameterIndex)
{
    if ((parameterIndex < 0) || (parameterIndex >= kernel->parameterCount))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return NULL;
    }

    GPUC_error = GPUC_Error_None;
    return kernel->parameters[parameterIndex].name;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Type
GPUC_Kernel_getParameterType(GPUC_Kernel kernel,
                             int         parameterIndex)
{
    if ((parameterIndex < 0) || (parameterIndex >= kernel->parameterCount))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return -1;
    }

    GPUC_error = GPUC_Error_None;
    return kernel->parameters[parameterIndex].type;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API int
GPUC_Kernel_getParameterLength(GPUC_Kernel kernel,
                               int         parameterIndex)
{
    if ((parameterIndex < 0) || (parameterIndex >= kernel->parameterCount))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return -1;
    }

    GPUC_error = GPUC_Error_None;
    return kernel->parameters[parameterIndex].length;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setOutputStream(GPUC_Kernel  kernel,
                            GPUC_Stream *streams,
                            int          streamCount)
{   
    int i;
    
    if (streamCount <= 0)
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }
    
    if (streamCount > GPUC_maxDrawBufferCount)
    {
        GPUC_error = GPUC_Error_UnsupportedStreamCount;
        return;
    }
    
    for (i = 1; i < streamCount; ++i)
    {
        if ((streams[i]->width != streams[0]->width) || (streams[i]->height != streams[0]->height))
        {
            GPUC_error = GPUC_Error_InvalidArgument;
            return;
        }
    }
    
    for (i = 0; i < kernel->outputStreamCount; ++i)
        GPUC_Stream_destroy(kernel->outputStreams[i]);
    
    for (i = 0; i < streamCount; ++i)
    {
        kernel->outputStreams[i] = streams[i];
        ++(streams[i]->referenceCount);
    }
    
    kernel->outputStreamCount = streamCount;
    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputStream(GPUC_Kernel  kernel,
                           int          parameterIndex,
                           GPUC_Stream *streams)
{
    int streamCount;
    int firstStreamIndex;
    int i;

    if ((parameterIndex < 0) ||
        (parameterIndex >= kernel->parameterCount) ||
        (kernel->parameters[parameterIndex].type != GPUC_Type_Stream))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    streamCount = kernel->parameters[parameterIndex].length;
    firstStreamIndex = kernel->parameters[parameterIndex].offset;

    for (i = 0; i < streamCount; ++i)
    {
        if (kernel->inputStreams[firstStreamIndex + i] != NULL)
            GPUC_Stream_destroy(kernel->inputStreams[firstStreamIndex + i]);
        
        kernel->inputStreams[firstStreamIndex + i] = streams[i];
        ++(streams[i]->referenceCount);
    }

    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

static GPUC_Bool
GPUC_Kernel_setInputPrologue(GPUC_Kernel kernel,
                             int         parameterIndex,
                             GPUC_Type   parameterType)
{
    if ((parameterIndex < 0) ||
        (parameterIndex >= kernel->parameterCount) ||
        (kernel->parameters[parameterIndex].type != parameterType))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return 0;
    }
    
    GPUC_ignoreGlErrors();
    glUseProgram(kernel->programId);
    
    if (GPUC_isGlError())
    {
        GPUC_error = GPUC_Error_Unknown;
        return 0;
    }
    
    return 1;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat(GPUC_Kernel  kernel,
                          int          parameterIndex,
                          const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float))
    {
        glUniform1fv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat2(GPUC_Kernel  kernel,
                           int          parameterIndex,
                           const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float2))
    {
        glUniform2fv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat3(GPUC_Kernel  kernel,
                           int          parameterIndex,
                           const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float3))
    {
        glUniform3fv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat4(GPUC_Kernel  kernel,
                           int          parameterIndex,
                           const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float4))
    {
        glUniform4fv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputInt(GPUC_Kernel kernel,
                        int         parameterIndex,
                        const int  *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Int))
    {
        glUniform1iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputInt2(GPUC_Kernel  kernel,
                         int          parameterIndex,
                         const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Int2))
    {
        glUniform2iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputInt3(GPUC_Kernel  kernel,
                         int          parameterIndex,
                         const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Int3))
    {
        glUniform3iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputInt4(GPUC_Kernel  kernel,
                         int          parameterIndex,
                         const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Int4))
    {
        glUniform4iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputBool(GPUC_Kernel  kernel,
                         int          parameterIndex,
                         const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Bool))
    {
        glUniform1iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputBool2(GPUC_Kernel  kernel,
                          int          parameterIndex,
                          const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Bool2))
    {
        glUniform2iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputBool3(GPUC_Kernel  kernel,
                          int          parameterIndex,
                          const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Bool3))
    {
        glUniform3iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputBool4(GPUC_Kernel  kernel,
                          int          parameterIndex,
                          const int   *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Bool4))
    {
        glUniform4iv(kernel->parameters[parameterIndex].uniformLocation,
                     kernel->parameters[parameterIndex].length, (const GLint *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat2x2(GPUC_Kernel  kernel,
                             int          parameterIndex,
                             const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float2x2))
    {
        glUniformMatrix2fv(kernel->parameters[parameterIndex].uniformLocation,
                           kernel->parameters[parameterIndex].length, GL_FALSE,
                           (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat3x3(GPUC_Kernel  kernel,
                             int          parameterIndex,
                             const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float3x3))
    {
        glUniformMatrix3fv(kernel->parameters[parameterIndex].uniformLocation,
                           kernel->parameters[parameterIndex].length, GL_FALSE,
                           (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_setInputFloat4x4(GPUC_Kernel  kernel,
                             int          parameterIndex,
                             const float *values)
{
    if (GPUC_Kernel_setInputPrologue(kernel, parameterIndex, GPUC_Type_Float4x4))
    {
        glUniformMatrix4fv(kernel->parameters[parameterIndex].uniformLocation,
                           kernel->parameters[parameterIndex].length, GL_FALSE,
                           (const GLfloat *)values);
        
        GPUC_error = GPUC_Error_None;
    }
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_invoke(GPUC_Kernel kernel)
{
    GPUC_Rectangle range;

    if (kernel->outputStreamCount == 0)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    range.x      = 0;
    range.y      = 0;
    range.width  = kernel->outputStreams[0]->width;
    range.height = kernel->outputStreams[0]->height;

    GPUC_Kernel_internalInvoke(kernel, &range);
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Kernel_partialInvoke(GPUC_Kernel           kernel,
                          const GPUC_Rectangle *range)
{   
    if (kernel->outputStreamCount == 0)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    if ((range->x < 0) ||
        (range->y < 0) ||
        (range->width < 0) ||
        (range->height < 0) ||
        ((range->x + range->width) > kernel->outputStreams[0]->width) ||
        ((range->y + range->height) > kernel->outputStreams[0]->height))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return;
    }

    GPUC_Kernel_internalInvoke(kernel, range);
}

/*------------------------------------------------------------------------------------------------*/

static void
GPUC_Kernel_internalInvoke(GPUC_Kernel           kernel,
                           const GPUC_Rectangle *range)
{
    int i;

    // Bind the output textures
    for (i = 0; i < kernel->outputStreamCount; ++i)
    {
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + i,
                                  GL_TEXTURE_RECTANGLE_ARB, kernel->outputStreams[i]->textureId,
                                  0);
    }

    glDrawBuffers(kernel->outputStreamCount, GPUC_drawBuffers);
    
    if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        glViewport(range->x, range->y, range->width, range->height);

        GPUC_ignoreGlErrors();
        glUseProgram(kernel->programId);

        if (GPUC_isGlError())
        {
            GPUC_error = GPUC_Error_Unknown;
        }
        else
        {
            // Bind the input textures
            for (i = 0; i < kernel->inputStreamCount; ++i)
            {
                if (kernel->inputStreams[i] == NULL)
                {
                    GPUC_error = GPUC_Error_InvalidOperation;
                    goto cleanup;
                }
                
                glActiveTexture(GL_TEXTURE0 + i);
                glBindTexture(GL_TEXTURE_RECTANGLE_ARB, kernel->inputStreams[i]->textureId);
            }

            // Draw a fullscreen quad
            glBegin(GL_QUADS);
                glVertex2f(-1, -1);
                glVertex2f(1, -1);
                glVertex2f(1, 1);
                glVertex2f(-1, 1);
            glEnd();

            GPUC_error = GPUC_Error_None;
        }
    }
    else
    {
        GPUC_error = GPUC_Error_UnsupportedStreamType;
    }
    
    // Unbind the output textures
cleanup:
    glDrawBuffer(GL_NONE);

    for (i = 0; i < kernel->outputStreamCount; ++i)
    {
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + i,
                                  GL_TEXTURE_RECTANGLE_ARB, 0, 0);
    }
}

/*------------------------------------------------------------------------------------------------*/

static GPUC_Bool
GPUC_uniformToParameterType(GLenum     uniformType,
                            GPUC_Type *parameterType)
{
    const struct
    {
        GLenum  uniformType;
        GPUC_Type parameterType;
    }
    map[] =
    {
        {GL_SAMPLER_2D_RECT_ARB, GPUC_Type_Stream  },
        {GL_FLOAT,               GPUC_Type_Float   },
        {GL_FLOAT_VEC2,          GPUC_Type_Float2  },
        {GL_FLOAT_VEC3,          GPUC_Type_Float3  },
        {GL_FLOAT_VEC4,          GPUC_Type_Float4  },
        {GL_INT,                 GPUC_Type_Int     },
        {GL_INT_VEC2,            GPUC_Type_Int2    },
        {GL_INT_VEC3,            GPUC_Type_Int3    },
        {GL_INT_VEC4,            GPUC_Type_Int4    },
        {GL_BOOL,                GPUC_Type_Bool    },
        {GL_BOOL_VEC2,           GPUC_Type_Bool2   },
        {GL_BOOL_VEC3,           GPUC_Type_Bool3   },
        {GL_BOOL_VEC4,           GPUC_Type_Bool4   },
        {GL_FLOAT_MAT2,          GPUC_Type_Float2x2},
        {GL_FLOAT_MAT3,          GPUC_Type_Float3x3},
        {GL_FLOAT_MAT4,          GPUC_Type_Float4x4}
    };

    const int mapLength = (int)(sizeof map / sizeof map[0]);
    int i;

    for (i = 0; i < mapLength; ++i)
    {
        if (map[i].uniformType == uniformType)
        {
            *parameterType = map[i].parameterType;
            return 1;
        }
    }

    return 0;
}

/*------------------------------------------------------------------------------------------------*/
